In questo articolo spiegherò come ho realizzato la libreria PW.JSON.dll che consente di gestire Stringhe e Oggetti secondo lo standard definito dal sito www.json.org.
Come si può leggere dal sito menzionato JSON è un linguaggio per rappresentare sottoforma di stringhe gli oggetti. Questo risulta molto utile quando si vuole per esempio scambiare dati complessi (oggetti) attraverso linguaggi differenti come ad esempio ASP.NET e Javascript o ActionScript e magari attraverso un protocollo che gestisce solo testo come l'HTTP.
Ho curiosato tra le librerie presenti nei link del sito www.json.org e ho visto che non ci sono molti esempi in VB.NET. Si trova una versione per VB 6, una per C# molto completa ma anche molto complessa, così alla fine ho trovato qualcosa di utile in questo sito http://mikeoncode.blogspot.com/2007/05/json-re-visited-from.html.
A questo codice che permette di generare un oggetto specifico per poi ottenere la corrispondente stringa JSON ho apportato alcune modifiche tra le quali ad esempio la possibilità di gestire le proprietà nulle (Nothing -> null) e la convenzione dei doppi apici (") anziché quelli singoli (') per la definizione dei nomi delle proprietà e dei corrispondenti valori stringa (così come espressamente indicato dalla definizione dello standard JSON).
Il risultato è il codice seguente:
Imports System.Text
Friend Class NetJSON
#Region " Private Declarations"
Dim myName As String = ""
Dim myContent As New Hashtable
Const dblQuote As Char = Chr(34)
#End Region
#Region " Enumerations"
Private Enum dataType
dt_Nothing
dt_Boolean
dt_Decimal
dt_Double
dt_Integer
dt_string
dt_Array
dt_NetJSON
End Enum
#End Region
#Region " Public Constructors, Methods and Properties"
Public Sub New()
End Sub
Public Sub New(ByVal nameString As String)
myName = nameString
End Sub
Public Sub AddNameValue(ByVal nameString As String, ByVal Value As Object)
StoreValue(nameString, Value)
End Sub
Public Overrides Function toString() As String
Dim Resp As New StringBuilder
Dim Firstcall As Boolean = True
Dim TailBrace As String = "}"
If myName.Length > 0 Then
Resp.Append("{" & dblQuote & myName & dblQuote & ": {")
TailBrace &= "}"
Else
Resp.Append("{")
End If
Dim myEnumerator As IDictionaryEnumerator = myContent.GetEnumerator()
While myEnumerator.MoveNext
Resp.Append(IIf(Firstcall, "", ", ") & dblQuote & myEnumerator.Key & dblQuote & ": " & MakeString(myEnumerator.Value))
Firstcall = False
End While
Resp.Append(TailBrace)
Return Resp.ToString()
End Function
#End Region
#Region " Private Functions and Subroutines"
Private Function MakeString(ByVal ThisData As Object) As String
Dim ThisType As dataType = GetDataType(ThisData)
If ThisType = dataType.dt_Array Then
Dim TestArray(ThisData.length) As Object
Dim aLoop As Int16
Dim ArrayStruct As New StringBuilder("[")
Dim FirstCall As Boolean = True
ThisType = GetDataType(ThisData(0))
For aLoop = 0 To ThisData.Length - 1
ArrayStruct.Append(IIf(FirstCall, "", ", ") & MakeElementString(ThisData(aLoop), ThisType))
FirstCall = False
Next
ArrayStruct.Append("]")
Return ArrayStruct.ToString()
Else
Return MakeElementString(ThisData, ThisType)
End If
End Function
Private Function MakeElementString(ByVal ThisData As Object, ByVal ThisDataType As dataType)
Select Case ThisDataType
Case dataType.dt_Boolean
Return IIf(CBool(ThisData), "true", "false")
Case dataType.dt_Decimal
Return String.Format("{0}", ThisData).Replace(","c, "."c)
Case dataType.dt_string
Return Chr(34) & CType(ThisData, String).Replace("\", "\\").Replace("/", "\/").Replace(vbCrLf, "\n").Replace(vbTab, "\t").Replace(Chr(34), "\" & Chr(34)) & Chr(34)
Case dataType.dt_Nothing
Return "null"
Case dataType.dt_NetJSON
Return ThisData.ToString()
Case Else
Return ""
End Select
End Function
Private Function GetDataType(ByVal Value As Object) As dataType
If TypeOf Value Is Array Then
Return dataType.dt_Array
ElseIf TypeOf Value Is Single Or TypeOf Value Is Double Then
Return dataType.dt_Double
ElseIf TypeOf Value Is Decimal Then
Return dataType.dt_Decimal
ElseIf TypeOf Value Is Boolean Then
Return dataType.dt_Boolean
ElseIf TypeOf Value Is String Then
Return dataType.dt_string
ElseIf TypeOf Value Is Integer Or TypeOf Value Is Int16 Or TypeOf Value Is Int32 Or TypeOf Value Is Int64 Then
Return dataType.dt_Integer
ElseIf TypeOf Value Is NetJSON Then
Return dataType.dt_NetJSON
ElseIf Value Is Nothing Then
Return dataType.dt_Nothing
End If
End Function
Private Sub StoreValue(ByVal nameString As String, ByVal Value As Object)
Select Case GetDataType(Value)
Case dataType.dt_Array
Dim copyArray(Value.length - 1) As Object
For aLoop As Int16 = 0 To Value.length - 1
copyArray(aLoop) = Value(aLoop)
Next
myContent.Add(nameString, copyArray)
Case dataType.dt_Boolean
Dim wrkBoolean As Boolean = CBool(Value)
myContent.Add(nameString, wrkBoolean)
Case dataType.dt_Double, dataType.dt_Integer, dataType.dt_Decimal
Dim wrkDecimal As Decimal = CDec(Value)
myContent.Add(nameString, wrkDecimal)
Case dataType.dt_string
Dim wrkString As String = CStr(Value)
myContent.Add(nameString, wrkString)
Case dataType.dt_NetJSON
myContent.Add(nameString, Value)
Case dataType.dt_Nothing
myContent.Add(nameString, Nothing)
End Select
End Sub
#End Region
End Class
Con la classe sopra descritta che è sostanzialmente un perfezionamento della classe scritta da Mike Griffiths, posso generare un oggetto di tipo NetJSON al quale aggiungo di volta in volta le proprietà che mi interessano. Notate che nel caso in cui si debba aggiungere una proprità di tipo complesso (un oggetto), tale proprietà deve essere comunque passata come tipo NetJSON in quanto è l'unico tipo di oggetto riconoscito dall'algoritmo.
Il vantaggio però che vorrei ottenere dall'uso di JSON è quello di poter trasformare qualsiasi oggetto in una stringa JSON. Per farlo ho costruito una classe JSONHelper con un metodo pubblico statico che, attraverso "reflection" converte un qualsiasi oggetto in una stringa JSON.
Ecco il codice della classe JSON, il metodo in questione è ObjectToString:
Imports System.Reflection
Public Class JSONHelper
#Region "Gestione Object via Reflection"
Public Shared Function ObjectToString(ByVal Obj As Object) As String
If Obj Is Nothing Then Return ""
Dim _t As Type = Obj.GetType()
Return ConvertSubObjectToNetJSON("", Obj).toString
End Function
Private Shared Function ConvertSubObjectToNetJSON(ByVal Name As String, ByRef Obj As Object) As NetJSON
If Obj Is Nothing Then Return Nothing
Dim _t As Type = Obj.GetType()
Dim result As New NetJSON(Name)
For Each _p As PropertyInfo In _t.GetProperties()
If _p.PropertyType.IsPrimitive Then
result.AddNameValue(_p.Name, _p.GetValue(Obj, Nothing))
ElseIf _p.PropertyType.IsArray Then
result.AddNameValue(_p.Name, _p.GetValue(Obj, Nothing))
ElseIf _p.PropertyType.IsClass AndAlso _p.PropertyType.Name <> GetType(String).Name Then
result.AddNameValue(_p.Name, ConvertSubObjectToNetJSON("", _p.GetValue(Obj, Nothing)))
ElseIf _p.PropertyType.Name = GetType(String).Name Then
result.AddNameValue(_p.Name, _p.GetValue(Obj, Nothing))
Else
ThrowNew NotImplementedException("Property Type '" & _p.PropertyType.Name & "' not yet implemented")
End If
Next
Return result
End Function
Public Shared Function StringToObject(ByVal JSONString As String, ByVal ClassType As Type) As Object
Dim ojson As New JSON.JSONObject
Return ojson.parse(JSONString, ClassType)
End Function
#End Region
End Class
Come si vede dal codice del metodo ObjectToString ed in particolare nel metodo privato ConvertSubObjectToNetJSON, si procede con l'esaminare tutte le proprietà dell'oggetto da convertire, utilizzando la classe PropertyInfo della libreria System.Reflection. Per ogni proprietà si verifica se si tratta di un tipo primitivo (Integer, Double, Single, ecc...), di un Array o di una classe (Stringa o altro) e si provvede ad aggiungere l'opportuno valore all'oggetto di appoggio NetJSON mediate il metodo AddNameValue.
Notare che per esempio per il tipo Date verrebbe rilasciata un eccezione di tipo NotImplementedException in quanto lo stesso linguaggio JSON non prevede il tipo data.
Vediamo ora un semplice esempio di utilizzo del metodo ObjectToString fin qui descritto:
Imports PW.JSON
Module Module1
Class Prova
Private _id As Integer
Private _name As String
Private _valido As Boolean
Private _subObject As Prova
Private _numero As Integer
Private _numeroDec As Double
Private _array() As String
Public Property ID() As Integer
Get
Return _id
End Get
Set(ByVal value As Integer)
_id = value
End Set
End Property
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Public Property Valido() As Boolean
Get
Return _valido
End Get
Set(ByVal value As Boolean)
_valido = value
End Set
End Property
Public Property SubObject() As Prova
Get
Return _subObject
End Get
Set(ByVal value As Prova)
_subObject = value
End Set
End Property
Public Property NumeroDec() As Double
Get
Return _numeroDec
End Get
Set(ByVal value As Double)
_numeroDec = value
End Set
End Property
Public Property Array() As String()
Get
Return _array
End Get
Set(ByVal value As String())
_array = value
End Set
End Property
Public Sub New(ByVal ID As Integer, ByVal Name As String)
_id = ID
_name = Name
End Sub
Public Function SomeMethod() As String
Return "Method: " & _id
End Function
End Class
Sub Main()
Dim objprova As New Prova(1, "Nome Object")
objprova.Array = Split("A E I O U")
objprova.NumeroDec = 100.34
objprova.SubObject = New Prova(2, "Nome - SubObject")
objprova.Valido = True
Console.WriteLine(PW.JSON.JSONHelper.ObjectToString(objprova))
Console.ReadLine()
End Sub
End Module
Il risultato che si ottiene in output è la seguente stringa JSON:
{"NumeroDec": 100.34, "Name": "Nome Object", "Array": ["A", "E", "I", "O", "U"], "SubObject": {"NumeroDec": 0, "Name": "Nome - SubObject", "Array": null, "SubObject": null, "Valido": false, "ID": 2}, "Valido": true, "ID": 1}
Ora vediamo invece il metodo inverso, ovvero creare un oggetto dalla stringa JSON, utilizzando la stringa prodotta dall'output precedente:
Sub Main()
Dim strJSON As String = "{""NumeroDec"": 100.34, ""Name"": ""Nome Object"", " & _
" ""Array"": [""A"", ""E"", ""I"", ""O"", ""U""], " & _
" ""SubObject"": {""NumeroDec"": 0, ""Name"": ""Nome - SubObject"", " & _
" ""Array"": null, ""SubObject"": null, ""Valido"": false, ""ID"": 2}, " & _
" ""Valido"": true, ""ID"": 1}"
Dim objprova As Prova
objprova = PW.JSON.JSONHelper.StringToObject(strJSON, GetType(Prova))
Console.WriteLine(objprova.Name)
Console.WriteLine(objprova.SubObject.Name)
Console.ReadLine()
End Sub
Il risultato ottenuto a video sarà il seguente:
Nome Object
Nome - SubObject
Ho utilizzato il metodo StringToObject della classe statica PW.JSON.JSONHelper per creare un oggetto di tipo Prova partendo dalla stringa JSON dell'esempio precedente.
A riprova del fatto che l'oggetto è stato creato correttamente con anche le proprietà complesse come SubObject (che è dello stesso tipo dell'oggetto proncipale), vediamo che a video vengono correttamente mostrati i valori delle prorietà Name.
Se desiderate visionare il codice sorgente completo della libreria o scaricare la versione compilata dovete essere utenti registrati e cliccare qui.